﻿// -------------------- iSharePoint -------------------- //
// 
//  Company:	IT Complex, LLC
//  Project:	SmartLib
//  File name:	SqlBulkInsert.cs
//  Developer:	Igor Solomatov
//  Created:	15.03.2013
// 
// -------------------- iSharePoint -------------------- //

using System;
using System.Collections.Generic;
using System.Data;
using System.Threading;

namespace Smart.SQL
{
	public class SqlBulkInsert
	{
		public SqlBulkInsert(SqlSettings settings, string tableName, int batchSize)
		{
			Settings = settings;
			TableName = tableName;
			BatchSize = batchSize;
			Dependencies = new List<SqlBulkInsert>();
		}

		public SqlSettings Settings { get; set; }
		public string TableName { get; set; }
		public int BatchSize { get; set; }
		public DataTable Storage { get; set; }
		public int TableRowsCount { get; set; }

		public bool IdentityColumnUsed { get; set; }
		public string IdentityColumnName { get; set; }
		public int IdentityColumnSeed { get; set; }

		public bool UniqueColumnUsed { get; set; }
		public bool UniqueValueCaseSensitive { get; set; }
		public string UniqueColumnName { get; set; }
		public Dictionary<object, int> UniqueValues { get; set; }

		public List<SqlBulkInsert> Dependencies { get; set; }

		public ReaderWriterLockSlim SyncObj = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);

		public void InitTable(Dictionary<string, Type> fields)
		{
			using (new WriteLock(SyncObj))
			{
				Storage = new DataTable(TableName);

				foreach (var field in fields)
				{
					Storage.Columns.Add(field.Key, field.Value);
				}
			}
		}

		public void SetIdentity(string identityName, int identitySeed)
		{
			using (new WriteLock(SyncObj))
			{
				IdentityColumnUsed = true;
				IdentityColumnName = identityName;
				IdentityColumnSeed = identitySeed;
			}
		}

		public void SetUnique(string uniqueName, bool caseSensitive)
		{
			using (new WriteLock(SyncObj))
			{
				UniqueColumnUsed = true;
				UniqueValueCaseSensitive = caseSensitive;
				UniqueColumnName = uniqueName;
				UniqueValues = new Dictionary<object, int>();
			}
		}

		public void SetDependencies(params SqlBulkInsert[] dependencies)
		{
			using (new WriteLock(SyncObj))
			{
				Dependencies.Clear();

				Dependencies.AddRange(dependencies);
			}
		}

		public void SetUnique(object key, int value)
		{
			using (new WriteLock(SyncObj))
			{
				if (UniqueValues == null)
					UniqueValues = new Dictionary<object, int>();

				if (!UniqueValueCaseSensitive && key is string)
					key = ((string)key).ToLower();

				UniqueValues[key] = value;
			}
		}

		public int GetIdentityByUnique(object value)
		{
			using (new ReadLock(SyncObj))
			{
				if (value == null || value == DBNull.Value)
					return -2;

				if (!UniqueValueCaseSensitive && value is string)
					value = ((string)value).ToLower();

				return UniqueValues.SafeGet(value, -1);
			}
		}

		public int Insert(Dictionary<string, object> row)
		{
			if (!CheckPurge())
				return -1;

			try
			{
				var newRow = Storage.NewRow();

				foreach (var fv in row)
					newRow[fv.Key] = fv.Value ?? DBNull.Value;

				var id = 0;

				using (new WriteLock(SyncObj))
				{
					if (IdentityColumnUsed)
						newRow[IdentityColumnName] = id = IdentityColumnSeed++;

					if (UniqueColumnUsed)
					{
						var uv = newRow[UniqueColumnName];

						if (uv != DBNull.Value)
						{
							// Unique key violation
							if (GetIdentityByUnique(uv) >= 0)
								return -1;

							if (!UniqueValueCaseSensitive && uv is string)
								uv = ((string)uv).ToLower();

							// Update unique keys
							UniqueValues[uv] = id;
						}
					}

					Storage.Rows.Add(newRow);

					TableRowsCount++;
				}

				return id;
			}
			catch (Exception x)
			{
				H.LX("SqlBulkInsert.Insert",
					 String.Concat("Insert TableName: ", TableName, " row: \n\t{\n", row.CastToString(",\n", pair => String.Format("\t\t{{{0}, {1}}}", pair.Key, pair.Value)), "\n\t}"),
					 x);
				return -1;
			}
		}

		public int InsertUnique(Dictionary<string, object> row)
		{
			if (!CheckPurge())
				return -1;

			try
			{
				var id = 0;

				using (new WriteLock(SyncObj))
				{
					object uv = null;

					if (UniqueColumnUsed)
					{
						uv = row[UniqueColumnName];

						if (uv != null && uv != DBNull.Value)
						{
							var eid = GetIdentityByUnique(uv);

							if (eid >= 0)
								return eid;
						}
					}

					var newRow = Storage.NewRow();

					foreach (var fv in row)
						newRow[fv.Key] = fv.Value ?? DBNull.Value;

					if (IdentityColumnUsed)
						row[IdentityColumnName] = id = IdentityColumnSeed++;

					if (UniqueColumnUsed)
					{
						if (uv != null && uv != DBNull.Value)
						{
							if (!UniqueValueCaseSensitive && uv is string)
								uv = ((string)uv).ToLower();

							// Update unique keys
							UniqueValues[uv] = id;
						}
					}

					Storage.Rows.Add(newRow);

					TableRowsCount++;
				}

				return id;
			}
			catch (Exception x)
			{
				H.LX("SqlBulkInsert.InsertUnique",
					 String.Concat("InsertUnique TableName: ", TableName, " row: \n\t{\n", row.CastToString(",\n", pair => String.Format("\t\t{{{0}, {1}}}", pair.Key, pair.Value)), "\n\t}"),
					 x);
				return -1;
			}
		}

		protected bool CheckPurge()
		{
			return
				TableRowsCount < BatchSize || Purge();
		}

		public bool Purge()
		{
			using (new WriteLock(SyncObj))
			{
				if (TableRowsCount <= 0)
					return true;

				foreach (var dependency in Dependencies)
				{
					if (!dependency.Purge())
						return false;
				}

				using (var sql = new SqlSession(Settings))
				{
					if (!sql.BulkCopy(TableName, Storage))
						return false;
				}

				TableRowsCount = 0;
				Storage.Rows.Clear();

				return true;
			}
		}
	}
}